﻿#region

using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using Lunexx.Network.Master;
using Lunexx.Network.World;

#endregion

namespace Lunexx.Network
{
    public abstract class Listener : IDisposable
    {
        #region Properties

        protected IPAddress ListenAddress
        {
            get { return m_listenAddress; }
            set
            {
                m_listenAddress = value;
                RestartListening();
            }
        }

        protected int ListenPort { get; set; }

        protected Socket ListenSocket
        {
            get { return m_listenSocket; }
            set { m_listenSocket = value; }
        }

        public bool Disposed { get; private set; }

        protected ArrayList Clients
        {
            get { return m_clients; }
        }

        public int ClientCount
        {
            get { return m_clients.Count; }
        }

        public bool Listening
        {
            get { return ListenSocket != null; }
        }

        public IPEndPoint Server { get; set; }


        #endregion

        #region Private Fields

        private readonly ArrayList m_clients = new ArrayList();
        private IPAddress m_listenAddress;
        private Socket m_listenSocket;

        #endregion

        #region Contructors & Deconstructors

        protected Listener(IPAddress listenAddress, int listenPort)
        {
            ListenPort = listenPort;
            m_listenAddress = listenAddress;
        }

        protected Listener(int listenPort, IPEndPoint serverIp, int serverPort)
            : this(IPAddress.Any, listenPort, serverIp)
        {
        }

        protected Listener(IPAddress listenAddress, int listenPort, IPEndPoint server)
            : this(listenAddress, listenPort)
        {
            Server = server;
        }
        protected Listener(IPAddress listenAddress, int listenPort, IPAddress serverAddress, int serverPort) : this(listenAddress,listenPort,new IPEndPoint(serverAddress,serverPort)) {}

        ~Listener()
        {
            Dispose();
        }

        #endregion

        #region Methods

        public void Dispose()
        {
            if (Disposed)
            {
                InfoWrite("Already Disposed (Can't Dispose)",3);
                return;
            }

            while (ClientCount > 0)
            {
                InfoWrite("Disposing of client", 3);
                ((Client) Clients[0]).Dispose();
            }

            try
            {
                InfoWrite("Shutting down Listen Sockets", 3);
                ListenSocket.Shutdown(SocketShutdown.Both);
            }

                // This should be logged to console!
            catch (Exception e)
            {
                ConsoleWrapper.DebugWrite(e.ToString(),1);
            }

            if (ListenSocket != null)
            {
                InfoWrite("Closing ListenSocket",3);
                ListenSocket.Close();
            }

            Disposed = true;
        }

        /// <summary>
        /// Bind to specified IP address and port and listen. When connections are made accept them and pass the 
        /// results to OnAccept.
        /// </summary>
        public void StartListening()
        {
            try
            {
                ListenSocket = new Socket(ListenAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                ListenSocket.Bind(new IPEndPoint(ListenAddress, ListenPort));
                ListenSocket.Listen(15);
                ListenSocket.BeginAccept(new AsyncCallback(OnAccept), ListenSocket);
                InfoWrite(string.Format("Started Listening on {0}:{1}",ListenAddress,ListenPort),2);

            }
            catch (Exception e)
            {
                ListenSocket = null;
                ConsoleWrapper.DebugWrite(e.ToString(),1);
            }
        }
        /// <summary>
        /// Restarts listening.
        /// </summary>
        protected void RestartListening()
        {
            if (ListenSocket == null)
            {
                return;
            }
            ListenSocket.Close();
            StartListening();
        }

        /// <summary>
        /// Checks to see if client is in client list, if not, add it.
        /// </summary>
        /// <param name="client"></param>
        protected void AddClient(Client client)
        {
            InfoWrite("Adding client: " + client.ToString(),3);
            if (Clients.IndexOf(client) == -1)
                Clients.Add(client);
        }
        /// <summary>
        /// remove client from client list.
        /// </summary>
        /// <param name="client"></param>
        protected void RemoveClient(Client client)
        {
            InfoWrite("Removing client: " + client.ToString(),3);
            Clients.Remove(client);
        }
        /// <summary>
        /// Write info to console
        /// </summary>
        /// <param name="input">Message to write</param>
        /// <param name="loglevel">loglevel required for it to be written</param>
        protected abstract void InfoWrite(string input, byte loglevel);
        /// <summary>
        /// Called when a client connection is trying to connect.
        /// </summary>
        /// <param name="asResult"></param>
        public abstract void OnAccept(IAsyncResult asResult);

        public override string ToString()
        {
            return "Listening on " + ListenAddress.ToString() + ":" + ListenPort.ToString();
        }

           
        #endregion
    }
}